package com.pig4cloud.pigx.wallet.listener;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.utils.BigDecimalUtils;
import com.pig4cloud.pigx.user.entity.User;
import com.pig4cloud.pigx.wallet.api.feign.RemoteRippleUserService;
import com.pig4cloud.pigx.wallet.configuration.SendServiceCharge;
import com.pig4cloud.pigx.wallet.constant.WalletConstant;
import com.pig4cloud.pigx.wallet.constant.WalletStatusEnum;
import com.pig4cloud.pigx.wallet.dto.ChargeMentionNotificationDTO;
import com.pig4cloud.pigx.wallet.dto.TronWithdrawal;
import com.pig4cloud.pigx.wallet.dto.WalletEmailQueueMessage;
import com.pig4cloud.pigx.wallet.entity.CapitalAccount;
import com.pig4cloud.pigx.wallet.entity.Currency;
import com.pig4cloud.pigx.wallet.entity.ImputationRecord;
import com.pig4cloud.pigx.wallet.entity.Notification;
import com.pig4cloud.pigx.wallet.entity.RechargeRecord;
import com.pig4cloud.pigx.wallet.entity.WithdrawalRecord;
import com.pig4cloud.pigx.wallet.mapstruct.NotificationStruct;
import com.pig4cloud.pigx.wallet.service.CapitalAccountService;
import com.pig4cloud.pigx.wallet.service.CurrencyService;
import com.pig4cloud.pigx.wallet.service.ImputationRecordService;
import com.pig4cloud.pigx.wallet.service.NotificationService;
import com.pig4cloud.pigx.wallet.service.RechargeRecordService;
import com.pig4cloud.pigx.wallet.service.WithdrawalRecordService;
import com.pig4cloud.pigx.wallet.utils.WalletEmailSender;
import com.rabbitmq.client.Channel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.logging.log4j.util.Strings;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.response.EthBlockNumber;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Objects;
import java.util.Optional;

@Slf4j
@Component
@RequiredArgsConstructor
public class WalletMQListener {

	private final RechargeRecordService rechargeRecordService;
	private final Web3j web3j;
	private final CapitalAccountService capitalAccountService;
	private final WithdrawalRecordService withdrawalRecordService;
	private final OkHttpClient okHttpClient;
	private final ImputationRecordService imputationRecordService;
	private final CurrencyService currencyService;
	private final RemoteRippleUserService remoteRippleUserService;
	private final RabbitTemplate rabbitTemplate;
	private final WalletEmailSender walletEmailSender;
	private final SendServiceCharge sendServiceCharge;
	/**
	 * 用户消息通知
	 */
	private final NotificationService notificationService;

	@RabbitListener(queues = "wallet.recharge.queue")
	@Transactional(rollbackFor = Exception.class)
	public void recharge(Long id, Channel channel, Message message) throws IOException {
		RechargeRecord rechargeRecord = rechargeRecordService.getById(id);

		String newestBlockNumber;

		if (WalletConstant.ETH.equals(rechargeRecord.getPublicChain())) {

			Request<?, EthBlockNumber> ethBlockNumberRequest = web3j.ethBlockNumber();

			EthBlockNumber send = ethBlockNumberRequest.send();

			newestBlockNumber = String.valueOf(send.getBlockNumber());
		} else {

			Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
					.eq(Currency::getPublicChain, WalletConstant.TRON)
					.eq(Currency::getState, WalletConstant.TRUE)
					.eq(Currency::getName, WalletConstant.USDT)
			);

			okhttp3.Request request = new okhttp3.Request.Builder()
					.url(currency.getNodeAddress() + "walletsolidity/getnowblock")
					.get()
					.addHeader("Accept", "application/json")
					.build();

			Response response = null;
			try {
				response = okHttpClient.newCall(request).execute();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			String res = null;
			try {
				res = response.body().string();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			newestBlockNumber = JSONUtil.parseObj(res).getJSONObject("block_header").getJSONObject("raw_data").getStr("number");
		}

		String blockNumber = rechargeRecord.getBlockNumber();

		BigDecimal sub = NumberUtil.sub(String.valueOf(newestBlockNumber), blockNumber);

		int compare = NumberUtil.compare(NumberUtil.parseLong(String.valueOf(sub)), NumberUtil.parseLong(rechargeRecord.getBlockConfirmationRechargeNumber()));
		if (compare < 0) {
			log.error("充值记录id:{}未达到区块确认数", id);
			channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
		} else {
			RechargeRecord rechargeRecordUpdate = new RechargeRecord();
			rechargeRecordUpdate.setId(id);
			rechargeRecordUpdate.setBlockConfirmationNumber(String.valueOf(sub));

			int rechargeCompare = rechargeRecord.getQuantity().compareTo(rechargeRecord.getMinimumRechargeNumber());

			if (rechargeCompare < 0) {
				log.error("未达到最小充值数,无法入账,充值记录id:{}", id);
				rechargeRecordUpdate.setState(WalletStatusEnum.RECHARGE_FAILED.getStatus());
				rechargeRecordService.updateById(rechargeRecordUpdate);

				// 封装充提通知参数
				R<User> user = remoteRippleUserService.getUserByUserId(rechargeRecord.getUserId(), SecurityConstants.FROM_IN);
				ChargeMentionNotificationDTO notificationDTO = new ChargeMentionNotificationDTO(rechargeRecord);
				notificationDTO.setState(WalletStatusEnum.RECHARGE_FAILED.getStatus());
				notificationDTO.setLanguage(user.getData().getLanguageCode());
				// 用户消息通知
				Notification notification = NotificationStruct.INSTANCE.chargeMentionDTO2PO(notificationDTO);
				notificationService.save(notification);

				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
				return;
			}

			rechargeRecordUpdate.setState(WalletStatusEnum.RECHARGE_COMPLETED.getStatus());
			rechargeRecordService.updateById(rechargeRecordUpdate);

			// 用户充值成功后给预支手续费
			if (rechargeRecord.getPublicChain().equals(WalletConstant.ETH)) {
				sendServiceCharge.ethFee(rechargeRecord);
			}
			if (rechargeRecord.getPublicChain().equals(WalletConstant.TRON)) {
				sendServiceCharge.tronFee(rechargeRecord);
			}

			// 封装充提通知参数
			R<User> user = remoteRippleUserService.getUserByUserId(rechargeRecord.getUserId(), SecurityConstants.FROM_IN);
			ChargeMentionNotificationDTO notificationDTO = new ChargeMentionNotificationDTO(rechargeRecord);
			notificationDTO.setState(WalletStatusEnum.RECHARGE_COMPLETED.getStatus());
			notificationDTO.setLanguage(user.getData().getLanguageCode());
			// 用户消息通知
			Notification notification = NotificationStruct.INSTANCE.chargeMentionDTO2PO(notificationDTO);
			notificationService.save(notification);

			log.info("充值记录id:{}达到区块确认数,开始上账", id);
			CapitalAccount capitalAccount = capitalAccountService.getOne(new LambdaQueryWrapper<CapitalAccount>()
					.eq(CapitalAccount::getUserId, rechargeRecord.getUserId())
			);
			BigDecimal quantity = rechargeRecord.getQuantity();
//			String frozen = capitalAccount.getFrozen();
			BigDecimal availableUpdate = capitalAccount.getAvailable().add(quantity);
//			BigDecimal totalUpdate = NumberUtil.add(frozen, String.valueOf(availableUpdate));
			CapitalAccount capitalAccountUpdate = new CapitalAccount();
			capitalAccountUpdate.setId(capitalAccount.getId());
			capitalAccountUpdate.setAvailable(availableUpdate);
//			capitalAccountUpdate.setTotalAssets(String.valueOf(totalUpdate));
			capitalAccountService.updateById(capitalAccountUpdate);

			WalletEmailQueueMessage walletEmailQueueMessage = new WalletEmailQueueMessage();
			walletEmailQueueMessage.setId(id);
			walletEmailQueueMessage.setType(WalletConstant.RECHARGE_SUCCESS);

			rabbitTemplate.convertAndSend("wallet.drive.exchange", "wallet.drive.email.binding", walletEmailQueueMessage);

			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		}

	}

	@RabbitListener(queues = "wallet.withdrawal.queue")
	public void withdrawal(Long id, Channel channel, Message message) throws IOException {

		WithdrawalRecord withdrawalRecord = withdrawalRecordService.getById(id);

		WithdrawalRecord withdrawalRecordUpdate = new WithdrawalRecord();
		withdrawalRecordUpdate.setId(id);

		if (WalletConstant.ETH.equals(withdrawalRecord.getPublicChain())) {
			Request<?, EthGetTransactionReceipt> ethGetTransactionReceiptRequest = web3j.ethGetTransactionReceipt(withdrawalRecord.getTransactionHash());
			EthGetTransactionReceipt send = ethGetTransactionReceiptRequest.send();
			Optional<TransactionReceipt> transactionReceipt = send.getTransactionReceipt();
			if (transactionReceipt.isPresent()) {

				log.info("提现成功,提现记录id：{}", id);

				withdrawalRecordUpdate.setState(WalletStatusEnum.WITHDRAWAL_COMPLETED.getStatus());
				withdrawalRecordService.updateById(withdrawalRecordUpdate);

				// 封装充提通知参数
				withdrawalRecord.setState(WalletStatusEnum.WITHDRAWAL_COMPLETED.getStatus());
				R<User> user = remoteRippleUserService.getUserByUserId(withdrawalRecord.getUserId(), SecurityConstants.FROM_IN);
				ChargeMentionNotificationDTO notificationDTO = new ChargeMentionNotificationDTO(withdrawalRecord);
				notificationDTO.setLanguage(user.getData().getLanguageCode());
				// 用户消息通知
				Notification notification = NotificationStruct.INSTANCE.chargeMentionDTO2PO(notificationDTO);
				notificationService.save(notification);

				WalletEmailQueueMessage walletEmailQueueMessage = new WalletEmailQueueMessage();
				walletEmailQueueMessage.setId(id);
				walletEmailQueueMessage.setType(WalletConstant.WITHDRAWAL_SUCCESS);

				rabbitTemplate.convertAndSend("wallet.drive.exchange", "wallet.drive.email.binding", walletEmailQueueMessage);

				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			} else {
				log.error("提现失败,提现记录id：{}", id);

				CapitalAccount capitalAccount = capitalAccountService.getOne(new LambdaQueryWrapper<CapitalAccount>()
						.eq(CapitalAccount::getUserId, withdrawalRecord.getUserId())
				);
				CapitalAccount capitalAccountUpdate = new CapitalAccount();
				capitalAccountUpdate.setId(capitalAccount.getId());

				BigDecimal addAvailable = capitalAccount.getAvailable().add(withdrawalRecord.getQuantity());

				capitalAccountUpdate.setAvailable(addAvailable);
				capitalAccountService.updateById(capitalAccountUpdate);

				withdrawalRecordUpdate.setState(WalletStatusEnum.WITHDRAWAL_FAILED.getStatus());
				withdrawalRecordService.updateById(withdrawalRecordUpdate);

				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			}
		} else {

			Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
					.eq(Currency::getPublicChain, WalletConstant.TRON)
					.eq(Currency::getState, WalletConstant.TRUE)
					.eq(Currency::getName, WalletConstant.USDT)
			);

			MediaType mediaType = MediaType.parse("application/json");
			RequestBody body = RequestBody.create(mediaType, "{\"value\":\"" + withdrawalRecord.getTransactionHash() + "\"}");

			okhttp3.Request request = new okhttp3.Request.Builder()
					.url(currency.getNodeAddress() + "walletsolidity/gettransactionbyid")
					.post(body)
					.addHeader("Accept", "application/json")
					.addHeader("Content-Type", "application/json")
					.build();

			Response response = null;
			try {
				response = okHttpClient.newCall(request).execute();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			String res = null;
			try {
				res = response.body().string();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			TronWithdrawal tronWithdrawal = JSONUtil.toBean(res, TronWithdrawal.class);

			if (!CollUtil.isNotEmpty(tronWithdrawal.getRet())) {
				log.error("提现交易哈希未完成确认，id：{}", id);
				channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
				return;
			}

			if ("SUCCESS".equals(tronWithdrawal.getRet().get(0).getContractRet())) {
				log.info("提现成功,提现记录id：{}", id);

				withdrawalRecordUpdate.setState(WalletStatusEnum.WITHDRAWAL_COMPLETED.getStatus());
				withdrawalRecordService.updateById(withdrawalRecordUpdate);

				// 封装充提通知参数
				withdrawalRecord.setState(WalletStatusEnum.WITHDRAWAL_COMPLETED.getStatus());
				R<User> user = remoteRippleUserService.getUserByUserId(withdrawalRecord.getUserId(), SecurityConstants.FROM_IN);
				ChargeMentionNotificationDTO notificationDTO = new ChargeMentionNotificationDTO(withdrawalRecord);
				notificationDTO.setLanguage(user.getData().getLanguageCode());
				// 用户消息通知
				Notification notification = NotificationStruct.INSTANCE.chargeMentionDTO2PO(notificationDTO);
				notificationService.save(notification);

				WalletEmailQueueMessage walletEmailQueueMessage = new WalletEmailQueueMessage();
				walletEmailQueueMessage.setId(id);
				walletEmailQueueMessage.setType(WalletConstant.WITHDRAWAL_SUCCESS);

				rabbitTemplate.convertAndSend("wallet.drive.exchange", "wallet.drive.email.binding", walletEmailQueueMessage);

				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			} else {
				log.error("提现失败,提现记录id：{}", id);

				CapitalAccount capitalAccount = capitalAccountService.getOne(new LambdaQueryWrapper<CapitalAccount>()
						.eq(CapitalAccount::getUserId, withdrawalRecord.getUserId())
				);
				CapitalAccount capitalAccountUpdate = new CapitalAccount();
				capitalAccountUpdate.setId(capitalAccount.getId());

				BigDecimal addAvailable = NumberUtil.add(capitalAccount.getAvailable(), withdrawalRecord.getQuantity());

				capitalAccountUpdate.setAvailable(addAvailable);
				capitalAccountService.updateById(capitalAccountUpdate);

				withdrawalRecordUpdate.setState(WalletStatusEnum.WITHDRAWAL_FAILED.getStatus());
				withdrawalRecordService.updateById(withdrawalRecordUpdate);

				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			}
		}
	}

	@RabbitListener(queues = "wallet.imputation.queue")
	public void imputation(Long id, Channel channel, Message message) throws IOException {
		// 归集列表
		ImputationRecord imputationRecord = imputationRecordService.getById(id);

		ImputationRecord imputationRecordUpdate = new ImputationRecord();
		imputationRecordUpdate.setId(id);

		// 获取 TRON公链 USDT币种信息
		Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
				.eq(Currency::getPublicChain, WalletConstant.TRON)
				.eq(Currency::getState, WalletConstant.TRUE)
				.eq(Currency::getName, WalletConstant.USDT)
		);

		MediaType mediaType = MediaType.parse("application/json");
		RequestBody body = RequestBody.create(mediaType, "{\"value\":\"" + imputationRecord.getTransactionHash() + "\"}");
		// 通过HASH获取交易信息
		okhttp3.Request request = new okhttp3.Request.Builder()
				.url(currency.getNodeAddress() + "walletsolidity/gettransactionbyid")
				.post(body)
				.addHeader("Accept", "application/json")
				.addHeader("Content-Type", "application/json")
				.build();

		Response response = null;
		try {
			response = okHttpClient.newCall(request).execute();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		String res = null;
		try {
			res = response.body().string();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		TronWithdrawal tronWithdrawal = JSONUtil.toBean(res, TronWithdrawal.class);

		if (!CollUtil.isNotEmpty(tronWithdrawal.getRet())) {
			log.error("归集交易哈希未完成确认，id：{}", id);
			// 拒绝消费当前消息，将数据重新丢回队列里
			channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
			return;
		}

		if ("SUCCESS".equals(tronWithdrawal.getRet().get(0).getContractRet())) {
			// 归集完成
			imputationRecordUpdate.setState(WalletStatusEnum.IMPUTATION_COMPLETED.getStatus());
			imputationRecordService.updateById(imputationRecordUpdate);
			// 手动确认消费
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		} else {
			// 归集失败
			imputationRecordUpdate.setState(WalletStatusEnum.IMPUTATION_FAILED.getStatus());
			imputationRecordService.updateById(imputationRecordUpdate);
			// 手动确认消费
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		}

	}

	/**
	 * 发送充提成功邮件
	 *
	 * @param walletEmailQueueMessage 消息对象
	 * @param channel                 mq连接
	 * @param message                 消息
	 * @since 2022/9/27 10:22
	 */
	@RabbitListener(queues = "wallet.email.queue")
	public void email(WalletEmailQueueMessage walletEmailQueueMessage, Channel channel, Message message) throws IOException {
		try {
			Long id = walletEmailQueueMessage.getId();
			String type = walletEmailQueueMessage.getType();

			if (Objects.isNull(id)) {
				log.error("钱包模块-发送邮件-消息异常：id为空");
			}
			if (Strings.isEmpty(type)) {
				log.error("钱包模块-发送邮件-消息异常：type为空");
			}

			// 充值成功
			if (type.equals(WalletConstant.RECHARGE_SUCCESS)) {
				// 查询充值记录
				RechargeRecord rechargeRecord = rechargeRecordService.getById(id);
				if (Objects.isNull(rechargeRecord)) {
					log.error("钱包模块-发送邮件-数据异常：rechargeRecord为空");
				}

				User user = remoteRippleUserService.getUserByUserId(rechargeRecord.getUserId(), SecurityConstants.FROM_IN).getData();
				// 发送邮件
				walletEmailSender.sendRecharge(BigDecimalUtils.toStrRemoveExtraZero(rechargeRecord.getQuantity()),
						rechargeRecord.getSourceAddress(),
						rechargeRecord.getTransactionHash(),
						user);

			} else if (type.equals(WalletConstant.WITHDRAWAL_SUCCESS)) {
				// 提现成功，查询提现记录
				WithdrawalRecord withdrawalRecord = withdrawalRecordService.getById(id);
				if (Objects.isNull(withdrawalRecord)) {
					log.error("钱包模块-发送邮件-数据异常：withdrawalRecord为空");
				}

				User user = remoteRippleUserService.getUserByUserId(withdrawalRecord.getUserId(), SecurityConstants.FROM_IN).getData();
				// 发送邮件
				walletEmailSender.sendWithdrawal(BigDecimalUtils.toStrRemoveExtraZero(withdrawalRecord.getActualWithdrawalAmount()),
						withdrawalRecord.getArrivalAddress(),
						withdrawalRecord.getTransactionHash(),
						user);
			}

			// 确认消息
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

		} catch (Throwable e) {
			e.printStackTrace();
			// 确认消息
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		}
	}
}
